package com.excel.validators.excel.entrance;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock;

import com.excel.validators.core.decorative.AbstractSheet;
import com.excel.validators.core.extension.ModelAnalysis;
import com.excel.validators.log.Log;
import com.excel.validators.log.LogFactory;

/**
 * Supports multithreaded, multi instance operations
 * 
 * @author wangxiaoliang10
 *
 * 2017年8月29日
 */
@SuppressWarnings({"rawtypes","unchecked"})
public abstract class ThreadLocalSheet {
	
		// single looger
		protected final static Log logger = LogFactory.getLog(AbstractSheet.class);
	
		protected ModelAnalysis initialValue() {
				ClassLoader loader = this.getClass().getClassLoader();
				try {
						return new ModelAnalysis(loader,getNamespace());
				} catch (Exception e) {
						return null;
				}
		}

		/*
		 * thread safe method ,get Excel model
		 */
		protected ModelAnalysis get() {
				ThreadLocalMap map = getMap();
				if (map != null) {
						 String t = getNamespace();
						 Object e = map.get(t);
				    if (e != null) {
				    			ModelAnalysis result = (ModelAnalysis) e;
				        return result;
				    }
				}
				return setInitialValue();
		}

    /**
     *
     * @return the initial ModelAnalysis
     */
		private ModelAnalysis setInitialValue() {
    			ModelAnalysis value = initialValue();
        String t = getNamespace();
        ThreadLocalMap map = getMap();
        if (map != null && value != null)
            map.put(t, value);
        else
            createMap(t, value);
        return value;
    }
		
		private static synchronized void createMap(String t, ModelAnalysis value) {
				if (threadLocals == null) {
						threadLocals = new ThreadLocalMap<String,ModelAnalysis>();
						if (value != null)
								threadLocals.put(t, value);
				}
		}

		/**
     * Sets the ModelAnalysisy of it namespace
     */
    protected synchronized void set(ModelAnalysis value) {
        ThreadLocalMap map = getMap();
        String t = getNamespace();
        if (map != null)
            map.put(t, value);
        else
            createMap(t, value);
    }

		/*
		* clean used object
		*/
		public void remove() {
				ThreadLocalMap m = getMap();
				if (m != null)
						m.remove(Thread.currentThread());
		}

		/*
		 * static models
		 */
		private static ThreadLocalMap<String,ModelAnalysis> threadLocals;
     
		/**
		 * Get the map associated
		 * @return the map
		 */
		ThreadLocalMap getMap() {
				return threadLocals;
		}

		/**
		 * 提供不精确的字符串等值比较
		 * @param o1
		 * @param o2
		 * @return
		 */
		protected boolean equals(String o1,String o2) {
				return o1.hashCode() == o2.hashCode();
		}

		
		protected abstract String getNamespace();
		
		protected abstract void setNamespace(String namespace);
    
		/**
		 * build based on softReference and map collection
		 * 
		 * @author wangxiaoliang10
		 *
		 * 2017年8月29日
		 */
		final static class ThreadLocalMap<K,V> implements Map<K,V> {
		
		    private static final int DEFAULT_RETENTION_SIZE = 8;
		
		    private final Map<K, SoftValue<V, K>> map;
		
		    private final int RETENTION_SIZE;
		    
		    private final Queue<V> strongReferences;
		    private final ReentrantLock strongReferencesLock;
		
		    private final ReferenceQueue<? super V> queue;
		
		    public ThreadLocalMap() {
		        this(DEFAULT_RETENTION_SIZE);
		    }
		
		    public ThreadLocalMap(int retentionSize) {
		        super();
		        RETENTION_SIZE = Math.max(0, retentionSize);
		        queue = new ReferenceQueue<V>();
		        strongReferencesLock = new ReentrantLock();
		        map = new ConcurrentHashMap<K, SoftValue<V, K>>();
		        strongReferences = new ConcurrentLinkedQueue<V>();
		    }
		
		    public ThreadLocalMap(Map<K, V> source) {
		        this(DEFAULT_RETENTION_SIZE);
		        putAll(source);
		    }
		
		    public ThreadLocalMap(Map<K, V> source, int retentionSize) {
		        this(retentionSize);
		        putAll(source);
		    }
		
		    public V get(Object key) {
		        processQueue();
		
		        V result = null;
		        SoftValue<V, K> value = map.get(key);
		
		        if (value != null) {
		            //unwrap the 'real' value from the SoftReference
		            result = value.get();
		            if (result == null) {
		                map.remove(key);
		            } else {
		                //Add this value to the beginning of the strong reference queue (FIFO).
		                addToStrongReferences(result);
		            }
		        }
		        return result;
		    }
		
		    private void addToStrongReferences(V result) {
		        strongReferencesLock.lock();
		        try {
		            strongReferences.add(result);
		            trimStrongReferencesIfNecessary();
		        } finally {
		            strongReferencesLock.unlock();
		        }
		
		    }
		
		    private void trimStrongReferencesIfNecessary() {
		        //trim the strong ref queue if necessary:
		        while (strongReferences.size() > RETENTION_SIZE) {
		            strongReferences.poll();
		        }
		    }
		
		    private void processQueue() {
		        SoftValue sv;
		        while ((sv = (SoftValue) queue.poll()) != null) {
		            //noinspection SuspiciousMethodCalls
		            map.remove(sv.key); // we can access private data!
		        }
		    }
		
		    public boolean isEmpty() {
		        processQueue();
		        return map.isEmpty();
		    }
		
		    public boolean containsKey(Object key) {
		        processQueue();
		        return map.containsKey(key);
		    }
		
		    public boolean containsValue(Object value) {
		        processQueue();
		        Collection values = values();
		        return values != null && values.contains(value);
		    }
		
		    public void putAll(Map<? extends K, ? extends V> m) {
		        if (m == null || m.isEmpty()) {
		            processQueue();
		            return;
		        }
		        for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
		            put(entry.getKey(), entry.getValue());
		        }
		    }
		
		    public Set<K> keySet() {
		        processQueue();
		        return map.keySet();
		    }
		
		    public Collection<V> values() {
		        processQueue();
		        Collection<K> keys = map.keySet();
		        if (keys.isEmpty()) {
		            //noinspection unchecked
		            return Collections.EMPTY_SET;
		        }
		        Collection<V> values = new ArrayList<V>(keys.size());
		        for (K key : keys) {
		            V v = get(key);
		            if (v != null) {
		                values.add(v);
		            }
		        }
		        return values;
		    }
		
		    public V put(K key, V value) {
		        processQueue(); // throw out garbage collected values first
		        SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);
		        SoftValue<V, K> previous = map.put(key, sv);
		        addToStrongReferences(value);
		        return previous != null ? previous.get() : null;
		    }
		
		    public V remove(Object key) {
		        processQueue(); // throw out garbage collected values first
		        SoftValue<V, K> raw = map.remove(key);
		        return raw != null ? raw.get() : null;
		    }
		
		    public void clear() {
		        strongReferencesLock.lock();
		        try {
		            strongReferences.clear();
		        } finally {
		            strongReferencesLock.unlock();
		        }
		        processQueue(); // throw out garbage collected values
		        map.clear();
		    }
		
		    public int size() {
		        processQueue(); // throw out garbage collected values first
		        return map.size();
		    }
		
		    public Set<Map.Entry<K, V>> entrySet() {
		        processQueue(); // throw out garbage collected values first
		        Collection<K> keys = map.keySet();
		        if (keys.isEmpty()) {
		            //noinspection unchecked
		            return Collections.EMPTY_SET;
		        }
		
		        Map<K, V> kvPairs = new HashMap<K, V>(keys.size());
		        for (K key : keys) {
		            V v = get(key);
		            if (v != null) {
		                kvPairs.put(key, v);
		            }
		        }
		        return kvPairs.entrySet();
		    }
		
		    private static class SoftValue<V, K> extends SoftReference<V> {
		
		        private final K key;
		
		        private SoftValue(V value, K key, ReferenceQueue<? super V> queue) {
		            super(value, queue);
		            this.key = key;
		        }
		    }
		}

}
